﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Configuration;
using SQLCompact;

namespace PictureOrganizer3
{
    public partial class ScanForm : Form
    {
        private log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private string selectedPath;
        private DateTime startTime = DateTime.Now;
        private DateTime fileScanStartTime;
        private int maxFolderDeep = Convert.ToInt32(ConfigurationManager.AppSettings["MaxFolderDeep"]);
        string fileSearchPattern = ConfigurationManager.AppSettings["FileSearchPattern"];
        char[] fileSearchPatternSeperator = ConfigurationManager.AppSettings["FileSearchPatternSeperator"].ToCharArray();
        private Database ctx;
        long fileCount = 0;
        long comicCount = 0;
        private int maxComicCount;
        

        public ScanForm(string selectedPath, Database ctx)
        {
            InitializeComponent();

            this.selectedPath = selectedPath;
            this.ctx = ctx;
            Text = String.Format(Text, selectedPath);
        }

        private void ScanForm_Load(object sender, EventArgs e)
        {
            scanDirectoryWorker.RunWorkerAsync();
        }

        private void btn_Cancel_Click(object sender, EventArgs e)
        {
            scanDirectoryWorker.CancelAsync();
        }

        #region Form-Functions
        delegate void SetTextCallback(Label label, string text);
        private void SetText(Label label, string text)
        {
            if (label.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                try
                {
                    this.Invoke(d, new object[] { label, text });
                }
                catch (ObjectDisposedException ex)
                {
                    logger.Warn("Das aktualisieren des Forms ist gescheitet, da es bereits gelöscht wurde.", ex);
                }
            }
            else
            {
                label.Text = text;
            }
        }

        delegate void SetProgressBarStyleCallback(ProgressBarStyle style);
        private void SetProgressBarStyle(ProgressBarStyle style)
        {
            if (this.pg_Global.InvokeRequired)
            {
                SetProgressBarStyleCallback d = new SetProgressBarStyleCallback(SetProgressBarStyle);
                try
                {
                    this.Invoke(d, new object[] { style });
                }
                catch (ObjectDisposedException ex)
                {
                    logger.Warn("Das aktualisieren des Forms ist gescheitet, da es bereits gelöscht wurde.", ex);
                }
            }
            else
            {
                this.pg_Global.Style = style;
            }
        }

        delegate void SetStepProgressCallback(int percent);
        private void SetStepProgress(int percent)
        {
            if (this.pg_CurrentStep.InvokeRequired)
            {
                SetStepProgressCallback d = new SetStepProgressCallback(SetStepProgress);
                try
                {
                    this.Invoke(d, new object[] { percent });
                }
                catch (ObjectDisposedException ex)
                {
                    logger.Warn("Das aktualisieren des Forms ist gescheitet, da es bereits gelöscht wurde.", ex);
                }
            }
            else
            {
                this.pg_CurrentStep.Value = percent;
            }
        }

        #endregion


        #region Backgroundworker

        private void scanDirectoryWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            logger.Info("Starte des Scanvorgang");
            // Style umschalten, da nicht bekannt ist, wieviele Ordner gescannt werden müssen.
            SetText(lbl_Status, "Gefundene Ordner: 0");
            SetProgressBarStyle(ProgressBarStyle.Marquee);
            var directoriesToScan = getSubFolders(new DirectoryInfo(selectedPath), 0);
            logger.InfoFormat("{0} Ordner wurden gefunden, welche Bilddateien beinhalten.", directoriesToScan.Count);
            // Und wieder zurück, da es nun bekannt ist
            SetProgressBarStyle(ProgressBarStyle.Blocks);

            fileScanStartTime = DateTime.Now;
            maxComicCount = directoriesToScan.Count;

            foreach (var dir in directoriesToScan)
            {
                // Prüfen, ob abgebrochen werden soll
                if (scanDirectoryWorker.CancellationPending) return;

                SetText(lbl_Status, String.Format("Scanne Ordner: {0}", dir.FullName));
                scanFolder(dir);

                comicCount++;
            }
        }

        private void scanDirectoryWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.pg_Global.Value = e.ProgressPercentage;
        }

        private void scanDirectoryWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            TimeSpan ts = new TimeSpan(DateTime.Now.Ticks - this.startTime.Ticks);
            logger.InfoFormat("Der Scanvorgang wurde abgebrochen oder ist beendet.\nZeit: {0} sek\nOrdner: {1}\nBilder:{2}",
                ts.TotalSeconds, comicCount, fileCount);
            this.Close();
        }

        private List<DirectoryInfo> getSubFolders(DirectoryInfo folder, int currentDeep)
        {
            List<DirectoryInfo> directoriesToScan = new List<DirectoryInfo>();
            // Aktuelle Ordnertiefe erhöhen
            logger.DebugFormat("Der Ordner {0} wird geprüft.", folder.FullName);
            currentDeep++;
            if (currentDeep > maxFolderDeep)
            {
                // Die maximale Tiefe ist erreicht, daher nun abbrechen.
                logger.WarnFormat("Die maximale Ordnertiefe von {0} Ordner(n) wurde erreicht. Tiefere Ordner werden ignoriert.", maxFolderDeep);
                return directoriesToScan;
            }

            if (scanDirectoryWorker.CancellationPending)
            {
                // Oder die Verarbeitung soll gestoppt werden
                logger.Warn("Die Verarbeitung wurde unterbrochen.");
                return directoriesToScan;
            }


            foreach (DirectoryInfo item in folder.GetDirectories())
            {
                // Alle Unterordner scannen
                directoriesToScan.AddRange(getSubFolders(item, currentDeep));
            }

            if (Utility.GetFiles(folder, fileSearchPattern, fileSearchPatternSeperator).Length > 0)
            {
                // Der aktuelle Ordner besitzt Bild-Dateien, daher muss er sich genauer angeschaut werden
                logger.DebugFormat("Der Ordner {0} beinhaltet Bilddateien.", folder.FullName);
                directoriesToScan.Add(folder);
                SetText(this.lbl_Status, String.Format("Gefundene Ordner: {0}", directoriesToScan.Count));
                UpdateStatistics(DateTime.Now, DateTime.Now, 0, 0);
            }

            return directoriesToScan;
        }

        private void UpdateStatistics(DateTime currentTime, DateTime currentFileScanTime, long maxFileCount, long currentfileCount)
        {
            TimeSpan ts = new TimeSpan(currentTime.Ticks - startTime.Ticks);
            DateTime dt = DateTime.MinValue.Add(ts);
            SetText(lbl_TimeElapsed, dt.ToString("HH:mm:ss"));

            if (currentfileCount > 0)
            {
                ts = new TimeSpan(currentFileScanTime.Ticks - startTime.Ticks);
                long averageTicksPerFile = ts.Ticks / currentfileCount;
                long estimateTicks = averageTicksPerFile * maxFileCount;
                long restTicks = estimateTicks - ts.Ticks;
                dt = DateTime.MinValue.Add(new TimeSpan(restTicks >= 0 ? restTicks : 0));
                SetText(lbl_TimeEstimate, dt.ToString("HH:mm:ss"));
            }
        }

        private void scanFolder(DirectoryInfo folder)
        {
            // Zuerst wird geschaut, ob der Ordner schon in der Datenbank existiert

            // Da dies nicht der Fall ist, wird geprüft, ob er eine index-Datei hat, um ihn nach einem 
            // Umzug doch noch erkennen zu können
            string comicGUID = Utility.GetComicGUID(folder.FullName);
            if (!string.IsNullOrWhiteSpace(comicGUID))
            {
                //comicAdapter.FillByComicKey(dbSet.Comic, comicGUID);
                var comic = ctx.Comics.FirstOrDefault(c => c.Id.ToString().Equals(comicGUID));

                if (comic != null)
                {
                    // Wenn der Ordner manuell umgezogen wurde, wird hier ggf. der Path aktualisiert
                    comic.DirectoryPath = folder.Parent.FullName;
                    ctx.SubmitChanges();

                    // Da der Comic ja bereits bekannt ist, kann das Scannen nach Bildern übersprungen werden
                    return;
                }
            }


            // Der Ordner ist noch völlig unbekannt und wird daher angelegt.
            var newComic = new Comic { Id = Guid.NewGuid(), Name = folder.Name, FolderName = folder.Name, DirectoryPath = folder.Parent.FullName };
            ctx.Comics.InsertOnSubmit(newComic);


            FileInfo[] files = Utility.GetFiles(folder, fileSearchPattern, fileSearchPatternSeperator);
            int currentPictureCount = 0;
            int maxPictureCount = files.Length;
            decimal averageFileCountPerFolder = (fileCount + maxPictureCount) / (comicCount + 1);
            long maxFileCount = Convert.ToInt64(averageFileCountPerFolder * maxComicCount);


            logger.InfoFormat("Der Ordner {0} beinhaltet {1} Bilder", folder.FullName, files.Length);

            foreach (FileInfo item in files)
            {
                if (scanDirectoryWorker.CancellationPending) return;

                fileCount++;

                // Neues Picture anlegen und bestücken
                var newPicture = new Picture { Id = Guid.NewGuid(), ComicId = newComic.Id, Name = item.Name, FileHash = Utility.SHA1StringHash(item.FullName) };


                if (newComic.DisplayImageId == null)
                    newComic.DisplayImageId = newPicture.Id;


                // und in DB packen
                ctx.Pictures.InsertOnSubmit(newPicture);
                currentPictureCount++;

                // Statistik berechnen
                decimal percentPicture = (decimal)currentPictureCount / (decimal)maxPictureCount * 100;
                decimal percentComicStep = 1 / (decimal)maxComicCount * 100;
                decimal percentComic = (decimal)comicCount / (decimal)maxComicCount * 100;
                percentComic += percentComicStep * (percentPicture / 100);
                // und ausgeben
                SetStepProgress(Convert.ToInt32(Math.Ceiling(percentPicture)));
                SetText(this.lbl_ProgressCurrentStep, string.Format("{0:0.00}%", Math.Round(percentPicture, 2)));
                scanDirectoryWorker.ReportProgress(Convert.ToInt32(Math.Ceiling(percentComic)));
                SetText(this.lbl_ProgressGlobal, string.Format("{0:0.00}%", Math.Round(percentComic, 2)));
                UpdateStatistics(DateTime.Now, DateTime.Now, maxFileCount, this.fileCount);
            }


            SetText(lbl_Status, "Daten werden gespeichert...");

            // Nachdem nun alles ausgelesen wurde muss das ganze in die Datenbank
            ctx.SubmitChanges();

            // Und noch die GUID in den Ordner setzen
            Utility.SetComicGUID(Path.Combine(newComic.DirectoryPath, newComic.FolderName), newComic.Id.ToString());
        }


        #endregion
    }
}
